﻿using System;
using System.Collections.Generic;
using System.IO;
using Assist;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using YArchitech.Revit;
using YArchitech.Revit.RevitExtension;
using YJKPresentation.CommonUnit;
using YJKPresentation.Utils;

namespace YJKPresentation.CrossDocument
{
	public class ParasiticEntitiesCopier
	{
		public ParasiticEntitiesCopier(ExternalCommandData cmdData, Autodesk.Revit.DB.Document sourceDoc, Transform transform, bool mirrored, double tolerance)
		{
			this.m_Revit = cmdData;
			this.m_CurrentDocument = this.m_Revit.Application.ActiveUIDocument.Document;
			this.m_SourceDocument = sourceDoc;
			this.m_CurrentViewHeight = Common.GetViewHeight(this.m_CurrentDocument, this.m_Revit.Application.ActiveUIDocument.ActiveView);
			this.m_Transform = transform;
			this.m_IsMirrored = mirrored;
			this.m_Tolerance = tolerance;
			this.m_Options = this.m_Revit.Application.Application.Create.NewGeometryOptions();
			this.m_Options.IncludeNonVisibleObjects = true;
			this.m_Options.ComputeReferences = true;
			this.m_Options.View = this.m_CurrentDocument.ActiveView;
		}

		public void CopyParasiticEntity(FamilyInstance parEnt, ParasiticEntitiesCopier.BaseOnType BaseOnType)
		{
			Family family = this.LoadEntityFamily(parEnt);
			if (family == null)
			{
				return;
			}
			FamilySymbol baseOnType = this.GetBaseOnType(parEnt, family);
			if (baseOnType == null)
			{
				return;
			}
			this.GreateNewEntity(parEnt, baseOnType, this.m_Transform, BaseOnType);
		}

		private Family FindFamily(Autodesk.Revit.DB.Document doc, string familyName)
		{
			FilteredElementCollector filteredElementCollector = new FilteredElementCollector(doc);
			filteredElementCollector.OfClass(typeof(Family));
			if (filteredElementCollector != null)
			{
				foreach (Element element in filteredElementCollector.ToElements())
				{
					if (((Family)element).Name == familyName)
					{
						return (Family)element;
					}
				}
			}
			return null;
		}

		private Family LoadEntityFamily(FamilyInstance parEnt)
		{
			Family family = null;
			string name = parEnt.Symbol.Family.Name;
			family = this.FindFamily(this.m_CurrentDocument, name);
			if (family != null)
			{
				return family;
			}
			string text = Path.GetTempPath() + name + ".rfa";
			Document document = this.m_SourceDocument.EditFamily(parEnt.Symbol.Family);
			document.SaveAs(text, new SaveAsOptions
			{
				OverwriteExistingFile = true
			});
			document.Close(false);
			try
			{
				Transaction transaction = new Transaction(this.m_CurrentDocument);
				transaction.Start("load families");
				this.m_CurrentDocument.LoadFamily(text, out family);
				transaction.Commit();
			}
			catch (Exception ex)
			{
				ex.ToString();
				File.Delete(text);
				return family;
			}
			File.Delete(text);
			return family;
		}

		private FamilySymbol GetBaseOnType(FamilyInstance parEnt, Family family)
		{
			FamilySymbol result = null;
			foreach (FamilySymbol familySymbol in family.GetSymbols())
			{
				if (parEnt.Symbol.Name.CompareTo(familySymbol.Name) == 0)
				{
					result = familySymbol;
					break;
				}
			}
			return result;
		}

		private void GreateNewEntity(FamilyInstance parEnt, FamilySymbol symbol, Transform transform, ParasiticEntitiesCopier.BaseOnType BaseOnType)
		{
			LocationPoint locationPoint = parEnt.Location as LocationPoint;
			XYZ xyz = Geometry.TransformPoint(locationPoint.Point, transform);
			XYZ xyz2 = new XYZ(xyz.X, xyz.Y, xyz.Z);
			Element element = this.FindHostEntity(parEnt, transform, BaseOnType, ref xyz2);
			if (element == null)
			{
				return;
			}
			xyz = new XYZ(xyz2.X, xyz2.Y, xyz.Z);
			Transaction transaction = new Transaction(this.m_CurrentDocument);
			FamilyInstance familyInstance = null;
			transaction.Start("CreateFamilyInstance");
			try
			{
				if (BaseOnType == ParasiticEntitiesCopier.BaseOnType.BASEON_WALL || BaseOnType == ParasiticEntitiesCopier.BaseOnType.BASEON_CEILING)
				{
					Geometry.RotateTo(XYZ.BasisY, locationPoint.Rotation, XYZ.BasisZ);
					UnifiedModified.ActivateFamilySymbol(symbol);
					FamilyInstance insertInstance = this.m_CurrentDocument.Create.NewFamilyInstance(xyz, symbol, element, parEnt.StructuralType);
					ParameterCopier.CopyParameters(parEnt, ref insertInstance);
					Common.UpdateModel(this.m_Revit.Application.ActiveUIDocument, false);
					familyInstance = this.SetInsertDirectionWithParasiticElement(insertInstance, parEnt);
				}
				else
				{
					XYZ xyz3 = parEnt.HandOrientation.CrossProduct(parEnt.FacingOrientation);
					xyz3 = Locator.GetNewDirection(xyz3, transform);
					Reference referenceFaceOfHost = this.GetReferenceFaceOfHost(element, xyz2, xyz3);
					XYZ newDirection = Locator.GetNewDirection(parEnt.HandOrientation, transform);
					UnifiedModified.ActivateFamilySymbol(symbol);
					familyInstance = this.m_Revit.Application.ActiveUIDocument.Document.Create.NewFamilyInstance(referenceFaceOfHost, xyz, newDirection, symbol);
					ParameterCopier.CopyParameters(parEnt, ref familyInstance);
				}
			}
			catch (Exception ex)
			{
				string message = ex.Message;
				if (transaction != null && transaction.HasStarted() && !transaction.HasEnded())
				{
					transaction.RollBack();
				}
				return;
			}
			if (familyInstance == null)
			{
				if (transaction != null && transaction.HasStarted() && !transaction.HasEnded())
				{
					transaction.RollBack();
				}
				return;
			}
			transaction.Commit();
		}

		private Reference GetReferenceFaceOfHost(Element host, XYZ pos, XYZ dir)
		{
			IEnumerator<GeometryObject> enumerator = host.get_Geometry(this.m_Options).GetEnumerator();
			List<GeometryObject> list = new List<GeometryObject>();
			while (enumerator.MoveNext())
			{
				GeometryObject item = enumerator.Current;
				list.Add(item);
			}
			double val = this.m_Tolerance;
			Reference result = null;
			foreach (GeometryObject geometryObject in list)
			{
				Solid solid = geometryObject as Solid;
				if (!(solid == null))
				{
					foreach (object obj in solid.Faces)
					{
						Face face = (Face)obj;
						if (!(face.GetType() != typeof(PlanarFace)))
						{
							PlanarFace planarFace = face as PlanarFace;
							IntersectionResult intersectionResult = planarFace.Project(pos);
							if (intersectionResult != null && planarFace.Reference != null && (planarFace.FaceNormal().IsAlmostEqualTo(dir) || planarFace.FaceNormal().IsAlmostEqualTo(-dir)))
							{
								if (Geometry.IsEqual(intersectionResult.Distance, 0.0))
								{
									return planarFace.Reference;
								}
								if (Geometry.Lessthan_Or_Equal(intersectionResult.Distance, val))
								{
									val = intersectionResult.Distance;
									result = planarFace.Reference;
								}
							}
						}
					}
				}
			}
			return result;
		}

		private FamilyInstance SetInsertDirectionWithParasiticElement(FamilyInstance insertInstance, FamilyInstance oldInstance)
		{
			LocationPoint locationPoint = insertInstance.Location as LocationPoint;
			LocationPoint locationPoint2 = oldInstance.Location as LocationPoint;
			XYZ xyz = Locator.GetNewDirection(oldInstance.FacingOrientation, this.m_Transform).Normalize();
			double rotation = locationPoint.Rotation;
			double num = locationPoint2.Rotation;
			bool flag = false;
			bool flag2 = false;
			if (this.m_IsMirrored)
			{
				XYZ ptOut = Geometry.RotateTo(xyz, Math.PI, XYZ.BasisZ);
				double angle = Geometry.getAngle(XYZ.Zero, ptOut);
				double num2 = Geometry.BetweenTheAngles(Geometry.getAngle(XYZ.Zero, oldInstance.FacingOrientation), angle, true);
				num = Geometry.formatAngle(num + num2);
				flag = !flag;
			}
			else
			{
				double angle2 = Geometry.getAngle(XYZ.Zero, xyz);
				double num3 = Geometry.BetweenTheAngles(Geometry.getAngle(XYZ.Zero, oldInstance.FacingOrientation), angle2, true);
				num = Geometry.formatAngle(num + num3);
			}
			if (Geometry.Lessthan_Or_Equal(Math.Abs(rotation - num), 0.0001))
			{
				if (oldInstance.Mirrored)
				{
					flag = !flag;
				}
			}
			else if (oldInstance.Mirrored)
			{
				flag2 = !flag2;
			}
			else
			{
				flag = !flag;
				flag2 = !flag2;
			}
			FamilyInstance result = insertInstance;
			if (flag && flag2)
			{
				FamilyInstance instance = this.MirrorFamilyInstanceSelf(insertInstance, true);
				result = this.MirrorFamilyInstanceSelf(instance, false);
			}
			else if (flag)
			{
				result = this.MirrorFamilyInstanceSelf(insertInstance, true);
			}
			else if (flag2)
			{
				result = this.MirrorFamilyInstanceSelf(insertInstance, false);
			}
			return result;
		}

		private FamilyInstance MirrorFamilyInstanceSelf(FamilyInstance instance, bool isHorizontal)
		{
			Plane referencePlaneWithParasiticElement;
			if (isHorizontal)
			{
				referencePlaneWithParasiticElement = this.GetReferencePlaneWithParasiticElement(instance, 0.0);
			}
			else
			{
				referencePlaneWithParasiticElement = this.GetReferencePlaneWithParasiticElement(instance, Math.PI*.5);
			}
			return MirrorOperater.MirrorParasiticElement(this.m_CurrentDocument, instance, referencePlaneWithParasiticElement, false, true);
		}

		private Plane GetReferencePlaneWithParasiticElement(FamilyInstance instance, double rotateAngle)
		{
			Plane result = null;
			XYZ facingOrientation = instance.FacingOrientation;
			if (facingOrientation == null)
			{
				return result;
			}
			LocationPoint locationPoint = instance.Location as LocationPoint;
			if (locationPoint == null)
			{
				return result;
			}
			XYZ point = locationPoint.Point;
			return RevitVersionFuncs.CreatePlanByNormalAndOrigin(Geometry.RotateTo(facingOrientation, rotateAngle, XYZ.BasisZ), point);
		}

		private bool GetHostObject(Element element, ref Element hostObject)
		{
			Element element2 = null;
			FamilyInstance familyInstance = element as FamilyInstance;
			if (familyInstance != null)
			{
				element2 = familyInstance.Host;
			}
			if (element2 == null)
			{
				return false;
			}
			hostObject = element2;
			return true;
		}

		private Element FindHostEntity(FamilyInstance parEnt, Transform transform, ParasiticEntitiesCopier.BaseOnType BaseOnType, ref XYZ newPosition)
		{
			Element result = null;
			Element element = null;
			this.GetHostObject(parEnt, ref element);
			if (element == null)
			{
				return result;
			}
			Element element2 = this.FindNewHostObject(this.m_CurrentDocument, element, parEnt, transform, BaseOnType, ref newPosition);
			if (element2 != null)
			{
				result = element2;
			}
			return result;
		}

		private Element FindNewHostObject(Autodesk.Revit.DB.Document doc, Element hostObj, Element subElement, Transform transform, ParasiticEntitiesCopier.BaseOnType BaseOnType, ref XYZ newPosition)
		{
			Location location = hostObj.Location;
			Element result;
			if (location.GetType() == typeof(LocationCurve))
			{
				FindCurveInfo newPosition2 = Locator.GetNewPosition((location as LocationCurve).Curve, transform, this.m_IsMirrored);
				result = this.FindNewHostObjectByCurve(doc, hostObj.GetType(), newPosition2, ref newPosition);
			}
			else if (location.GetType() == typeof(LocationPoint))
			{
				XYZ newPosition3 = Locator.GetNewPosition((location as LocationPoint).Point, transform);
				result = this.FindNewHostObjectByPoint(doc, hostObj.GetType(), newPosition3, ref newPosition);
			}
			else
			{
				Line ray = YJKLineEx.YJKGetUnBound(newPosition, XYZ.BasisZ);
				result = this.FindNewHostObjectByRay(doc, hostObj.GetType(), ray);
			}
			return result;
		}

		private Element FindNewHostObjectByCurve(Autodesk.Revit.DB.Document doc, Type hostObjectType, FindCurveInfo newLocation, ref XYZ position)
		{
			IList<Element> elements = new FilteredElementCollector(doc, this.m_Revit.Application.ActiveUIDocument.ActiveView.Id).OfClass(hostObjectType).ToElements();
			List<Element> elements2 = new List<Element>();
			this.FilterHostObjectByView(elements, ref elements2);
			return this.FindNewHostObjectByCurve(elements2, newLocation, ref position);
		}

		private Element FindNewHostObjectByPoint(Autodesk.Revit.DB.Document doc, Type hostObjectType, XYZ newLocation, ref XYZ newPosition)
		{
			IList<Element> elements = new FilteredElementCollector(doc, this.m_Revit.Application.ActiveUIDocument.ActiveView.Id).OfClass(hostObjectType).ToElements();
			List<Element> elements2 = new List<Element>();
			this.FilterHostObjectByView(elements, ref elements2);
			return this.FindNewHostObjectByPoint(elements2, newLocation, ref newPosition);
		}

		private void FilterHostObjectByView(IList<Element> elements, ref List<Element> filteredEles)
		{
			double elevation = this.m_Revit.Application.ActiveUIDocument.ActiveView.GenLevel.Elevation;
			double val = elevation + this.m_CurrentViewHeight;
			foreach (Element element in elements)
			{
				BoundingBoxXYZ boundingBoxXYZ = element.get_BoundingBox(this.m_Revit.Application.ActiveUIDocument.ActiveView);
				if (!Geometry.Lessthan_Or_Equal(boundingBoxXYZ.Max.Z, elevation) && !Geometry.Greaterthan_Or_Equal(boundingBoxXYZ.Min.Z, val))
				{
					filteredEles.Add(element);
				}
			}
		}

		private Element FindNewHostObjectByRay(Autodesk.Revit.DB.Document doc, Type hostObjectType, Line ray)
		{
			IList<Element> elements = new FilteredElementCollector(doc, this.m_Revit.Application.ActiveUIDocument.ActiveView.Id).OfClass(hostObjectType).ToElements();
			return this.FindNewHostObjectByRay(elements, ray);
		}

		private Element FindNewHostObjectByRay(IList<Element> elements, Line ray)
		{
			foreach (Element element in elements)
			{
				IEnumerator<GeometryObject> enumerator2 = element.get_Geometry(this.m_Options).GetEnumerator();
				List<GeometryObject> list = new List<GeometryObject>();
				while (enumerator2.MoveNext())
				{
					GeometryObject item = enumerator2.Current;
					list.Add(item);
				}
				foreach (GeometryObject geometryObject in list)
				{
					Solid solid = geometryObject as Solid;
					if (!(solid == null))
					{
						foreach (object obj in solid.Faces)
						{
							Face face = (Face)obj;
							if (!(face.GetType() != typeof(PlanarFace)))
							{
								PlanarFace planarFace = face as PlanarFace;
								IntersectionResultArray intersectionResultArray;
                                if (planarFace.ComputeNormal(new UV(planarFace.Origin.X, planarFace.Origin.Y)).IsAlmostEqualTo(XYZ.BasisZ) && (int)planarFace.Intersect(ray, out intersectionResultArray) == 8)
								{
									return element;
								}
							}
						}
					}
				}
			}
			return null;
		}

		private Element FindNewHostObjectByCurve(IList<Element> elements, FindCurveInfo newLocation, ref XYZ position)
		{
			double val = this.m_Tolerance;
			Element element = null;
			FindCurveInfo rhs = null;
			foreach (Element element2 in elements)
			{
				LocationCurve locationCurve = element2.Location as LocationCurve;
				if (locationCurve != null)
				{
					FindCurveInfo findCurveInfo = new FindCurveInfo(locationCurve.Curve);
					if (findCurveInfo.IsOverlap(newLocation, position))
					{
						return element2;
					}
					double num = 0.0;
					if (findCurveInfo.IsParallel2ndOverlap(newLocation, position, ref num) && Geometry.Lessthan_Or_Equal(num, val))
					{
						val = num;
						rhs = findCurveInfo;
						element = element2;
					}
				}
			}
			if (element != null)
			{
				position += newLocation.GetOffset(rhs, position);
			}
			return element;
		}

		private Element FindNewHostObjectByPoint(IList<Element> elements, XYZ newLocation, ref XYZ position)
		{
			double val = this.m_Tolerance;
			Element element = null;
			XYZ xyz = null;
			foreach (Element element2 in elements)
			{
				LocationPoint locationPoint = element2.Location as LocationPoint;
				if (locationPoint != null)
				{
					double num = locationPoint.Point.DistanceTo(newLocation);
					if (locationPoint.Point.IsAlmostEqualTo(newLocation))
					{
						return element2;
					}
					if (Geometry.Lessthan_Or_Equal(num, val))
					{
						val = num;
						xyz = locationPoint.Point;
						element = element2;
					}
				}
			}
			if (element != null)
			{
				position += xyz - newLocation;
			}
			return element;
		}

		private ExternalCommandData m_Revit;

		private Document m_SourceDocument;

		private Document m_CurrentDocument;

		private double m_CurrentViewHeight = -1.0;

		private double m_Tolerance;

		private Transform m_Transform;

		private bool m_IsMirrored;

		private Options m_Options;

		public enum BaseOnType
		{
			BASEON_WALL,
			BASEON_CEILING,
			BASEON_SURFACE
		}
	}
}
